/**
 * File: ReflectionUtils.java
 * Created by: mhaimel
 * Created on: 2 Jul 2009
 * CVS:  $Id: ReflectionUtils.java,v 1.1 2009/08/05 15:16:11 mhaimel Exp $
 */
package uk.ac.ebi.curtain.util;

import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Arrays;

import org.apache.commons.lang.ArrayUtils;

import uk.ac.ebi.velvet.VelvetUncheckedException;

import com.google.inject.cglib.reflect.FastClass;

/**
 * 
 * @see adapted from Proteomes ServicesClient package
 * @author mhaimel
 * 
 */
public class ReflectionUtils {
	
	private static final Object[] EMPTY_OBJECT_ARRAY = ArrayUtils.EMPTY_OBJECT_ARRAY;
	
	/**
	 * Wrapper for {@link #newInstance(String, Object[])} where params are empty
	 * arrays with the exception of className
	 */
	@SuppressWarnings("unchecked")
	public static <T> T newInstance(String className)throws VelvetUncheckedException {
		return newInstance((Class<T>)getClassFromName(className), EMPTY_OBJECT_ARRAY);
	}
	
	/**
	 * Wrapper for the {@link Class#forName(String)} but wraps exceptions in the
	 * runtime exception and will throw one if the generated class was null; I'm
	 * not even sure if this is a possible return from
	 * {@link Class#forName(String)}
	 */
	public static  Class<?> getClassFromName(String className)throws VelvetUncheckedException {
		try {
			Class<?> generatedClass = Class.forName(className);
			if (generatedClass == null) {
				throw new VelvetUncheckedException(
						"Generated class was null for " + className);
			} else {
				return generatedClass;
			}
		} catch (ClassNotFoundException e) {
			throw new VelvetUncheckedException(
					"Could not find class for name " + className, e);
		}
	}
	
	/**
	 * Wrapper for {@link #newInstance(Class, Object[])} but giving it empty
	 * Class and Object arrays
	 */
	public static <T> T newInstance(Class<T> target)
			throws VelvetUncheckedException {
		return newInstance(target, EMPTY_OBJECT_ARRAY);
	}
	
	/**
	 * Runs {@link #newInstance(Class, Class[], Object[])} but constructs the
	 * types array from the input arguments. You may not want to do this if your
	 * constructor uses an interface or superclass as a constructor argument
	 */
	public static <T> T newInstance(Class<T> target, Object[] args) throws VelvetUncheckedException {
		Class<?>[] types = generateClassArrayFromObject(args);
		return newInstance(target, types, args);
	}
	
	/**
	 * Generates a Class[] from the input Object[]
	 */
	private static Class<?>[] generateClassArrayFromObject(Object[] args) {
		Class<?>[] output = new Class[args.length];
		for (int i = 0; i < args.length; i++) {
			output[i] = args[i].getClass();
		}
		return output;
	}	
	
	/**
	 * Creates an instance of the given class with the argument types and values
	 * using CGLIB's {@link FastClass}
	 */
	public static <T> T newInstance(Class<T> target, Class<?>[] types,
			Object[] args) throws VelvetUncheckedException {
		T createdObject = null;

		try {
			createdObject = target.getConstructor(types).newInstance(args);
		} catch (IllegalArgumentException e) {
			throwInstantiationException("Exception detected", target, types,
					args, e);
		} catch (SecurityException e) {
			throwInstantiationException("Exception detected", target, types,
					args, e);
		} catch (InstantiationException e) {
			throwInstantiationException("Exception detected", target, types,
					args, e);
		} catch (IllegalAccessException e) {
			throwInstantiationException("Exception detected", target, types,
					args, e);
		} catch (NoSuchMethodException e) {
			throwInstantiationException("Exception detected", target, types,
					args, e);
		} catch (InvocationTargetException e) {
			throwInstantiationException("Exception detected", target, types,args, e);
		}

		if (createdObject == null) {
			throwInstantiationException("Created object was null", target,types, args);
		}

		return createdObject;
	}
	
	private static void throwInstantiationException(String reason,
			Class<?> target, Class<?>[] types, Object[] args) {
		throwInstantiationException(reason, target, types, null);
	}
	
	private static void throwInstantiationException(String reason,
			Class<?> target, Class<?>[] types, Object[] args, Exception e) {
		String msg = MessageFormat.format(
						"Could not instantiate instance of {0} with class types {1} and values {2}. Reason: {3}",
						new Object[] { target.getName(),
								Arrays.toString(types), Arrays.toString(args),
								reason });
		if (e == null) {
			throw new VelvetUncheckedException(msg);
		} else {
			throw new VelvetUncheckedException(msg, e);
		}
	}
}
